package nz.ac.massey.jfontview;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;

/**
 * A component which allows a user to preview and select fonts.
 *
 * @author Nathan C Jones
 * @version 0.3
 */
public class FontSelector extends JComponent {

	private static final long serialVersionUID = 5354511995301451279L;

	/**
	 * The set of pre-defined font sizes used in the size selection list.
	 */
	private static final Integer[] FONT_SIZES = new Integer[]{8,10,12,14,16,18,20,24,28,32,36,42,48};

	/**
	 * The mutable font which represents the state of this component.
	 */
	private final MutableFont mutableFont;

	/**
	 * The list of favourite fonts.
	 */
	private FontList favouriteFonts;

	/**
	 * Create a new font selector with a new default mutable font
	 * and an empty favourites list.
	 * @see nz.ac.massey.jfontview.DefaultMutableFont.getDefaultFont()
	 */
	public FontSelector(){
		this(new DefaultMutableFont());
	}

	/**
	 * Create a new font selector with the specified mutable font
	 * and an empty favourites list.
	 * @param mutableFont the mutable font to be manipulated by the font selector.
	 */
	public FontSelector(MutableFont mutableFont){
		this(mutableFont,new FontList());
	}

	/**
	 * Create a new font selector with the specified favourite fonts list
	 * and a new default mutable font.
	 * @param favourites the initial list of favourite fonts.
	 */
	public FontSelector(FontList favourites){
		this(new DefaultMutableFont(),favourites);
	}

	/**
	 * Create a new font selector with the specified mutable font and
	 * the specified favourite fonts list.
	 * @param mutableFont the mutable font to be manipulated by the font selector.
	 * @param favouriteFonts the initial list of favourite fonts.
	 */
	public FontSelector(MutableFont mutableFont,FontList favouriteFonts){
		this.mutableFont = mutableFont;
		this.favouriteFonts = favouriteFonts;
		init();
		mutableFont.fireFontChanged();
	}

	/**
	 * Get the currently selected font.
	 * This font will be described in the font specification area,
	 * and shown in the preview area.
	 * @return the currently selected font.
	 */
	public Font getSelectedFont(){
		return mutableFont.getFont();
	}

	/**
	 * Get the mutable font used by the font selector.
	 * By retrieving the mutable font addition mutable font listeners
	 * can be added to it.
	 * @return a mutable font.
	 */
	public MutableFont getMutableFont(){
		return mutableFont;
	}

	/**
	 * Get the list of favourite fonts
	 * @return the favourite fonts list.
	 */
	public FontList getFavouriteFonts(){
		return favouriteFonts;
	}

	/**
	 * @param favouriteFonts the favouriteFonts to set
	 */
	public void setFavouriteFonts(FontList favouriteFonts) {
		this.favouriteFonts = favouriteFonts;
	}

	private void init() {
		initialiseFonts();
		setLayout(new BorderLayout());
		add(createFontSelectionArea(),BorderLayout.NORTH);
		JSplitPane bottomPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true,createFavouriteFontsArea(),createPreviewArea());
		bottomPane.setBorder(BorderFactory.createEmptyBorder());
		add(bottomPane,BorderLayout.CENTER);
	}

	private void initialiseFonts() {
		//TODO force the jvm to cache the fonts for faster first-time rendering while scrolling
		GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
	}

	private JComponent createFontSelectionArea(){
		JPanel fontSelectionPanel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();

		c.fill = GridBagConstraints.BOTH;
		c.weightx = 1;
		c.weighty = 1;
		c.gridx = 0;
		c.gridy = 0;
		c.gridwidth = 1;
		c.gridheight = 3;
		fontSelectionPanel.add(createNameSelector(),c);

		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0;
		c.weighty = 0;
		c.anchor = GridBagConstraints.FIRST_LINE_START;
		c.gridx = 1;
		c.gridheight = 1;
		fontSelectionPanel.add(createStyleSelector(),c);

		c.gridy = 1;
		fontSelectionPanel.add(createSizeSelector(),c);

		return fontSelectionPanel;
	}

	private JComponent createNameSelector(){
		JPanel nameSelectorPanel = new JPanel(new BorderLayout());
		nameSelectorPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.GRAY),"Font Name"));
		final JList fontNameList = new JList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
		JScrollPane fontFamilyScrollPane = new JScrollPane(fontNameList);
		nameSelectorPanel.add(fontFamilyScrollPane,BorderLayout.CENTER);
		fontNameList.addListSelectionListener(new ListSelectionListener(){
			public void valueChanged(ListSelectionEvent e) {
				mutableFont.setName((String)fontNameList.getSelectedValue());
			}
		});
		mutableFont.addMutableFontListener(new MutableFontListener(){
			public void fontChanged(){
				fontNameList.setSelectedValue(mutableFont.getName(),true);
			}
		});
		return nameSelectorPanel;
	}

	private JComponent createStyleSelector(){
		JPanel styleSelectorPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
		styleSelectorPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.GRAY),"Font Style"));
		final JCheckBox boldCheckBox = new JCheckBox("Bold");
		final JCheckBox italicCheckBox = new JCheckBox("Italic");
		ActionListener changeStyleAction =new ActionListener(){
			public void actionPerformed(ActionEvent e){
				int newStyle = Font.PLAIN;
				if(boldCheckBox.isSelected())
					newStyle = newStyle|Font.BOLD;
				if(italicCheckBox.isSelected())
					newStyle = newStyle|Font.ITALIC;
				mutableFont.setStyle(newStyle);
			}
		};
		boldCheckBox.addActionListener(changeStyleAction);
		italicCheckBox.addActionListener(changeStyleAction);
		mutableFont.addMutableFontListener(new MutableFontListener(){
			public void fontChanged(){
				switch(mutableFont.getStyle()){
				case Font.PLAIN:
					boldCheckBox.setSelected(false);
					italicCheckBox.setSelected(false);
					break;
				case Font.BOLD:
					boldCheckBox.setSelected(true);
					italicCheckBox.setSelected(false);
					break;
				case Font.ITALIC:
					boldCheckBox.setSelected(false);
					italicCheckBox.setSelected(true);
					break;
				case Font.BOLD|Font.ITALIC:
					boldCheckBox.setSelected(true);
					italicCheckBox.setSelected(true);
					break;
				default:
					throw new RuntimeException("Invalid Font Style");
				}
			}
		});
		styleSelectorPanel.add(boldCheckBox);
		styleSelectorPanel.add(italicCheckBox);
		return styleSelectorPanel;
	}

	private JComponent createSizeSelector(){
		JPanel sizeSelectorPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
		sizeSelectorPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.GRAY),"Font Size"));
		final JComboBox sizeBox = new JComboBox(FONT_SIZES);
		sizeBox.setEditable(true);
		sizeBox.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				mutableFont.setSize((Integer)sizeBox.getSelectedItem());
			}
		});
		mutableFont.addMutableFontListener(new MutableFontListener(){
			public void fontChanged(){
				sizeBox.getEditor().setItem(mutableFont.getSize());
			}
		});
		sizeSelectorPanel.add(sizeBox);
		return sizeSelectorPanel;
	}

	private JComponent createPreviewArea(){
		JPanel previewAreaPanel = new JPanel(new BorderLayout());
		previewAreaPanel.setPreferredSize(new Dimension(250,100));
		previewAreaPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(),"Preview"));
		final JEditorPane previewPane = new JEditorPane(){
			private static final long serialVersionUID = 1863797212831024065L;
			@Override
			public void paint(Graphics g){
				Graphics2D g2 = (Graphics2D)g;
				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
				super.paint(g);
			}
		};
		try {
			previewPane.getDocument().insertString(0,"The Quick Brown Fox Jumps Over The Lazy Dog",null);
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
		previewPane.setBorder(BorderFactory.createLineBorder(Color.GRAY));
		previewPane.setFont(mutableFont.getFont());
		mutableFont.addMutableFontListener(new MutableFontListener(){
			public void fontChanged(){
				previewPane.setFont(mutableFont.getFont());
			}
		});
		previewAreaPanel.add(previewPane);
		return previewAreaPanel;
	}

	private JComponent createFavouriteFontsArea(){
		JPanel favouritesPanel = new JPanel(new BorderLayout());
		favouritesPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.GRAY),"Favourites"));

		final JTable fontTable = new JTable(favouriteFonts);
		fontTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

		JScrollPane scrollPane = new JScrollPane(fontTable);
		scrollPane.setBorder(BorderFactory.createEmptyBorder());
		scrollPane.setPreferredSize(new Dimension(150,100));
		favouritesPanel.add(scrollPane,BorderLayout.CENTER);

		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		JButton addFavouriteButton = new JButton("Add");
		addFavouriteButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				favouriteFonts.add(mutableFont.getFont());
				fontTable.getSelectionModel().setSelectionInterval(favouriteFonts.getSize()-1,favouriteFonts.getSize()-1);
			}
		});
		buttonPanel.add(addFavouriteButton);
		final JButton removeFavouriteButton = new JButton("Remove");
		removeFavouriteButton.setEnabled(false);
		removeFavouriteButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				int index = fontTable.getSelectedRow();
				if(index>=0){
					favouriteFonts.remove(index);
					index = index>=favouriteFonts.getSize()?index-1:index;
					if(index>=0)
						fontTable.getSelectionModel().setSelectionInterval(index,index);
				}
			}
		});
		buttonPanel.add(removeFavouriteButton);
		favouritesPanel.add(buttonPanel, BorderLayout.SOUTH);

		fontTable.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
			public void valueChanged(ListSelectionEvent e) {
				if(fontTable.getSelectedRow()>=0){
					mutableFont.setFont(favouriteFonts.get(fontTable.getSelectedRow()));
					removeFavouriteButton.setEnabled(true);
				}
				else
					removeFavouriteButton.setEnabled(false);
			}
		});
		
		mutableFont.addMutableFontListener(new MutableFontListener(){
			public void fontChanged(){
				int index = fontTable.getSelectedRow();
				if(index>=0 && !getSelectedFont().equals(favouriteFonts.get(index))){
					fontTable.getSelectionModel().clearSelection();
				}
			}
		});

		return favouritesPanel;
	}
}
